package com.javasea.netty.har.common.endec;

import com.google.protobuf.MessageLite;
import com.javasea.netty.har.common.constant.CmdConstant;
import com.javasea.netty.har.common.entity.proto.*;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

/**
 * 参考ProtobufVarint32FrameDecoder 和 ProtobufDecoder
 * 并且为它创建了一个4个字节的包头，格式如下
 * body长度（low）----> body长度（high）----> 保留字节 ----> 类型
 */
@Slf4j
public class CustomProtobufDecoder extends ByteToMessageDecoder {

    /**
     * 数据包基本长度
     */
    public static int BASE_LENTH = 4 + 4;

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 如果可读长度小于包头长度，退出。
        while (in.readableBytes() > BASE_LENTH) {
            //第一个可读数据包的起始位置
            int beginIndex;
            ByteBuf bodyByteBuf = null;
            try {
                // 通过固定包头解决可能存在的分包问题
                while(true) {
                    //包头开始游标点
                    beginIndex = in.readerIndex();
                    //标记初始读游标位置
                    in.markReaderIndex();
                    if (in.readInt() == CmdConstant.HEADER_FLAG) {
                        break;
                    }

                    //未读到包头标识略过一个字节
                    in.resetReaderIndex();
                    in.readByte();

                    //不满足，返回父类，继续缓冲数据
                    if(in.readableBytes() < BASE_LENTH){
                        return ;
                    }
                }

                // 获取包头中的body长度
                byte low = in.readByte();
                byte high = in.readByte();
                short s0 = (short) (low & 0xff);
                short s1 = (short) (high & 0xff);
                s1 <<= 8;
                short length = (short) (s0 | s1);

                // 获取包头中的protobuf类型
                in.readByte();
                byte dataType = in.readByte();

                // 如果可读长度小于body长度，恢复读指针，返回父类继续缓冲。
                if (in.readableBytes() < length) {
//                    in.resetReaderIndex();
                    in.readerIndex(beginIndex);
                    return;
                }

                // 读取body
                // 有时候报错：LEAK: ByteBuf.release() was not called before it's garbage-collected
                bodyByteBuf = in.readBytes(length);

                byte[] array;
                int offset;

                int readableLen= bodyByteBuf.readableBytes();
                if (bodyByteBuf.hasArray()) {
                    array = bodyByteBuf.array();
                    offset = bodyByteBuf.arrayOffset() + bodyByteBuf.readerIndex();
                } else {
                    array = new byte[readableLen];
                    bodyByteBuf.getBytes(bodyByteBuf.readerIndex(), array, 0, readableLen);
                    offset = 0;
                }

                //反序列化
                MessageLite result = decodeBody(dataType, array, offset, readableLen);
                out.add(result);
            } catch (Exception e) {
                log.error("解码发生异常！", e);
            } finally {
                // 读完就释放, 否则报错：LEAK: ByteBuf.release() was not called before it‘s garbage-collected
                // 发现netty5默认的分配bytebuff的方式是PooledByteBufAllocator,所以要手动回收，要不然会造成内存泄漏
                if(bodyByteBuf != null){
                    try {
                        bodyByteBuf.release();
                    } catch (Exception e) {
                        log.error("", e);
                    }
                }
            }
        }
    }

    public MessageLite decodeBody(byte dataType, byte[] array, int offset, int length) throws Exception {
        // 0x00 打印命令批次
        if (dataType == CmdConstant.Cmd.CMD_BATCH) {
            return CmdBatchProto.CmdBatch.getDefaultInstance().
                    getParserForType().parseFrom(array, offset, length);
            // 0x01 命令，目前是查询在线命令用的Command
        } else if (dataType == CmdConstant.Cmd.COMMAND) {
            return CommandProto.Command.getDefaultInstance().
                    getParserForType().parseFrom(array, offset, length);
            // 0x02 客户端 发 服务端 打印状态汇报信息
        } else if (dataType == CmdConstant.Cmd.PRINT_STATE_MSG_NOTICE) {
            return PrintStateMsgProto.PrintStateMsgNotice.getDefaultInstance().
                    getParserForType().parseFrom(array, offset, length);
            // 0x03 服务端 发 客户端 汇报状态信息确认收到
        } else if (dataType == CmdConstant.Cmd.PRINT_STATE_MSG_CONFIRM) {
            return PrintStateMsgProto.PrintStateMsgConfirm.getDefaultInstance().
                    getParserForType().parseFrom(array, offset, length);
            // 0x04 服务端 和 客户端 心跳
        } else if (dataType == CmdConstant.Cmd.HEART) {
            return HeartProto.Heart.getDefaultInstance().
                    getParserForType().parseFrom(array, offset, length);
            // 0x05 客户端 发 服务端 命令执行结果
        } else if (dataType == CmdConstant.Cmd.RESULT) {
            return ResultProto.Result.getDefaultInstance().
                    getParserForType().parseFrom(array, offset, length);
            // 0x06 客户端上线消息
        } else if (dataType == CmdConstant.Cmd.NIO_CLIENT) {
            return NioClientProto.NioClient.getDefaultInstance().
                    getParserForType().parseFrom(array, offset, length);
        }
        // or throw exception
        throw new RuntimeException("不支持的命令类型: "+dataType);
    }
}
